package com.ikingtech.framework.sdk.wechat.embedded.mini;

import com.fasterxml.jackson.core.type.TypeReference;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.ikingtech.framework.sdk.context.exception.FrameworkException;
import com.ikingtech.framework.sdk.enums.wechat.WechatMiniSubscribeMessageTemplateTypeEnum;
import com.ikingtech.framework.sdk.utils.Tools;
import com.ikingtech.framework.sdk.wechat.embedded.mini.request.WechatMiniQrcodeGenerateParam;
import com.ikingtech.framework.sdk.wechat.embedded.mini.request.WechatMiniSendMessageParam;
import com.ikingtech.framework.sdk.wechat.embedded.mini.request.WechatMiniUrlLinkGenerateParam;
import com.ikingtech.framework.sdk.wechat.embedded.mini.resposne.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 微信相关操作
 *
 * @author tie yan
 */
@Slf4j
@RequiredArgsConstructor
public class WechatMiniRequest {

    private WechatMiniConfig config;

    private static final Cache<String, String> ACCESS_TOKEN_CACHE = Caffeine.newBuilder().expireAfterWrite(6000, TimeUnit.SECONDS).build();

    private String getAccessToken() {
        String accessToken = ACCESS_TOKEN_CACHE.getIfPresent(this.config.getAppId());
        if (Tools.Str.isBlank(accessToken)) {
            String resultStr;
            try {
                resultStr = Tools.Http.get(Tools.Str.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={}&secret={}", this.config.getAppId(), this.config.getAppSecret()));
            } catch (Exception e) {
                throw new FrameworkException(Tools.Str.format("[WECHAT][小程序][{}]获取accessToken异常[{}]", this.config.getAppId(), e.getMessage()));
            }
            if (Tools.Str.isBlank(resultStr)) {
                throw new FrameworkException(Tools.Str.format("[WECHAT][小程序][{}]获取accessToken无响应", this.config.getAppId()));
            }
            Map<String, Object> result = Tools.Json.toMap(resultStr);
            if (null == result) {
                throw new FrameworkException(Tools.Str.format("[WECHAT][小程序][{}]获取accessToken结果为空", this.config.getAppId()));
            }
            accessToken = (String) result.get("access_token");
            if (Tools.Str.isBlank(accessToken)) {
                throw new FrameworkException(Tools.Str.format("[WECHAT][小程序][{}]获取accessToken为空", this.config.getAppId()));
            }
            ACCESS_TOKEN_CACHE.put(this.config.getAppId(), accessToken);
        }
        return accessToken;
    }

    public void sendSubscribeMessage(WechatMiniSendSubscribeMessageArgs args) {
        String resultStr;
        Map<String, Map<String, Object>> data = new HashMap<>(args.getParams().size());
        // 转换为{ "key1": { "value": any }, "key2": { "value": any } }形式的Map
        args.getParams().forEach((key, value) -> data.put(key, Tools.Coll.newMap(Tools.Coll.Kv.of("value", value))));
        WechatMiniSendMessageParam wechatParam = new WechatMiniSendMessageParam();
        wechatParam.setTemplateId(args.getTemplateId());
        wechatParam.setPage(args.getRedirectTo());
        wechatParam.setTouser(args.getOpenId());
        wechatParam.setData(Tools.Json.toJsonStr(data));
        wechatParam.setMiniprogramState("formal");
        wechatParam.setLang("zh_CN");
        try {
            resultStr = Tools.Http.post(Tools.Str.format("https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token={}", this.getAccessToken()), Tools.Json.toJsonStr(wechatParam));
        } catch (Exception e) {
            throw new FrameworkException(Tools.Str.format("[WECHAT][小程序]send wechat mini subscribe message exception[param={}][exception={}].", wechatParam, e.getMessage()));
        }
        if (Tools.Str.isBlank(resultStr)) {
            throw new FrameworkException(Tools.Str.format("[WECHAT][小程序]send wechat mini subscribe message without response[{}].", wechatParam));
        }
        WechatMiniResponse<Object> result = Tools.Json.toBean(resultStr, new TypeReference<>() {
        });
        if (null == result) {
            throw new FrameworkException(Tools.Str.format("[WECHAT][小程序]send wechat mini subscribe message with blank result[{}:{}]", this.config.getAppId(), this.config.getAppSecret()));
        }
        if (result.fail()) {
            throw new FrameworkException(Tools.Str.format("[WECHAT][小程序]send wechat mini subscribe message fail[{}:{}][{}]", this.config.getAppId(), this.config.getAppSecret(), result.getErrmsg()));
        }
    }

    public WechatMiniResponseSession exchangeCode(String code) {
        String resultStr;
        try {
            resultStr = Tools.Http.get(Tools.Str.format("https://api.weixin.qq.com/sns/jscode2session?appid={}&secret={}&js_code={}&grant_type=authorization_code", this.config.getAppId(), this.config.getAppSecret(), code));
        } catch (Exception e) {
            throw new FrameworkException(Tools.Str.format("[WECHAT][小程序]exchange wechat mini code exception[code={}][exception={}].", code, e.getMessage()));
        }
        if (Tools.Str.isBlank(resultStr)) {
            throw new FrameworkException("[WECHAT][小程序]exchange wechat mini code without response." + code);
        }
        return Tools.Json.toBean(resultStr, new TypeReference<>() {});
    }

    public WechatMiniUserPhoneInfo getPhone(String code) {
        String resultStr;
        try {
            resultStr = Tools.Http.post(Tools.Str.format("https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token={}", this.getAccessToken()), Tools.Json.toJsonStr(Tools.Coll.newMap(Tools.Coll.Kv.of("code", code))));
        } catch (Exception e) {
            throw new FrameworkException(Tools.Str.format("[WECHAT][小程序]get user phone by code exception[code={}][exception={}].", code, e.getMessage()));
        }
        if (Tools.Str.isBlank(resultStr)) {
            throw new FrameworkException("[WECHAT][小程序]get user phone by code without response[code={}]." + code);
        }
        WechatMiniResponseUserPhone response = Tools.Json.toBean(resultStr, new TypeReference<>() {
        });
        if (null == response) {
            throw new FrameworkException("[WECHAT][小程序]get user phone by code without response[code={}]." + code);
        }
        if (response.fail()) {
            throw new FrameworkException(Tools.Str.format("[WECHAT][小程序]get user phone by code fail[code={}][{}].", code, response.getErrmsg()));
        }
        if (null == response.getPhoneInfo()) {
            throw new FrameworkException(Tools.Str.format("[WECHAT][小程序]get user phone by code without phone info[code={}][{}].", code, response.getErrmsg()));
        }
        WechatMiniUserPhoneInfo userPhoneInfo = new WechatMiniUserPhoneInfo();
        userPhoneInfo.setPhone(response.getPhoneInfo().getPhoneNumber());
        userPhoneInfo.setPurePhone(response.getPhoneInfo().getPurePhoneNumber());
        userPhoneInfo.setCountryCode(response.getPhoneInfo().getCountryCode());
        return userPhoneInfo;
    }

    public String qrcodeGenerate(WechatMiniQrcodeGenerateArgs args) {
        WechatMiniQrcodeGenerateParam wechatParam = new WechatMiniQrcodeGenerateParam();
        wechatParam.setPage(args.getPage());
        wechatParam.setScene(args.getParam());
        wechatParam.setWidth(args.getWidth());
        wechatParam.setEnvVersion(Tools.Str.isBlank(args.getVersion()) ? "release" : args.getVersion());
        wechatParam.setCheckPath(false);
        try {
            byte[] result = Tools.Http.postForBytes(Tools.Str.format("https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token={}", this.getAccessToken()), Tools.Json.toJsonStr(wechatParam));
            return Base64.getEncoder().encodeToString(result);
        } catch (Exception e) {
            throw new FrameworkException(Tools.Str.format("[WECHAT][小程序]create qrcode exception[exception={}].", e.getMessage()));
        }
    }

    public String urlLinkGenerate(WechatMiniUrlLinkGenerateArgs args) {
        WechatMiniUrlLinkGenerateParam wechatParam = new WechatMiniUrlLinkGenerateParam();
        wechatParam.setPath(args.getPage());
        wechatParam.setQuery(args.getParam());
        wechatParam.setExpireType(1);
        wechatParam.setExpireInterval(30);
        String resultStr;
        try {
            resultStr = Tools.Http.post(Tools.Str.format("https://api.weixin.qq.com/wxa/generate_urllink?access_token={}", this.getAccessToken()), Tools.Json.toJsonStr(wechatParam));
        } catch (Exception e) {
            throw new FrameworkException(Tools.Str.format("[WECHAT][小程序]get url link exception[code={}][exception={}].", Tools.Json.toJsonStr(wechatParam), e.getMessage()));
        }
        if (Tools.Str.isBlank(resultStr)) {
            throw new FrameworkException("[WECHAT][小程序]get url link without response[code={}]." + Tools.Json.toJsonStr(wechatParam));
        }
        WechatMiniResponseUrlLink response = Tools.Json.toBean(resultStr, new TypeReference<>() {
        });
        if (null == response) {
            throw new FrameworkException("[WECHAT][小程序]get url link without response[code={}]." + Tools.Json.toJsonStr(wechatParam));
        }
        if (response.fail()) {
            throw new FrameworkException(Tools.Str.format("[WECHAT][小程序]get url link fail[code={}][{}].", Tools.Json.toJsonStr(wechatParam), response.getErrmsg()));
        }
        return response.getUrlLink();
    }

    public List<WechatMiniSubscribeMessageTemplateInfo> listSubscribeMessageTemplate() {
        String resultStr;
        try {
            resultStr = Tools.Http.get(Tools.Str.format("https://api.weixin.qq.com/wxaapi/newtmpl/gettemplate?access_token={}", this.getAccessToken()));
        } catch (Exception e) {
            throw new FrameworkException(Tools.Str.format("[WECHAT][小程序]list subscribe message template exception[{}:{}][{}]", this.config.getAppId(), this.config.getAppSecret(), e.getMessage()));
        }
        if (Tools.Str.isBlank(resultStr)) {
            throw new FrameworkException(Tools.Str.format("[WECHAT][小程序]list subscribe message template without response[{}:{}]", this.config.getAppId(), this.config.getAppSecret()));
        }
        WechatMiniResponse<List<WechatMiniResponseSubscribeMessageTemplate>> result = Tools.Json.toBean(resultStr, new TypeReference<>() {
        });
        if (result == null) {
            throw new FrameworkException(Tools.Str.format("[WECHAT][小程序]list subscribe message template without response[{}:{}]", this.config.getAppId(), this.config.getAppSecret()));
        }
        if (result.fail()) {
            throw new FrameworkException(Tools.Str.format("[WECHAT][小程序]list subscribe message template fail[{}:{}][{}]", this.config.getAppId(), this.config.getAppSecret(), result.getErrmsg()));
        }

        return Tools.Coll.convertList(result.getData(), template -> {
            WechatMiniSubscribeMessageTemplateInfo templateInfo = new WechatMiniSubscribeMessageTemplateInfo();
            templateInfo.setTemplateId(template.getPriTmplId());
            templateInfo.setTemplateTitle(template.getTitle());
            templateInfo.setContent(template.getContent());
            if (2 == template.getType()) {
                templateInfo.setType(WechatMiniSubscribeMessageTemplateTypeEnum.ONE_TIME);
            } else {
                templateInfo.setType(WechatMiniSubscribeMessageTemplateTypeEnum.LONG_TERM);
            }
            templateInfo.setParams(Tools.Str.findPlaceholder(template.getContent(), "{{", "}}", param -> Tools.Str.removeSuffix(param, ".DATA")));
            return templateInfo;
        });
    }

    public WechatMiniRequest config(WechatMiniConfig config) {
        this.config = config;
        return this;
    }
}
